﻿using System;
using System.Collections.Generic;
using System.Text;
using LibNoise;
using SampleImageGenerator.Modules;
using SampleImageGenerator.Modules.Primitives;
using SampleImageGenerator.Modules.Operations;
using System.Drawing;

namespace SampleImageGenerator.Factories
{
	internal class ModuleFactory
	{
		private Int32 salt = 0xbeef;

		private delegate IModule MathModuleCreator(Random random, RectangleF region);

		public IModule GetModule(Int32 seed, RectangleF region)
		{
			// This module factory generates modules based on
			// mathematical primitives with a little noise 
			// added on top. 

			Random random = new Random(seed ^ salt);

			MathModuleCreator[] moduleCreators = new MathModuleCreator[]
			{
				SinewaveCreator,
				DistanceCreator,
				GaussianCreator,
				PerlinCreator,
			};

			Int32 moduleCount = random.Next(2, 6);
			List<IModule> modules = new List<IModule>();
			for (Int32 ii = 0; ii < moduleCount; ii++)
			{
				Int32 creatorIndex = random.Next(0, moduleCreators.Length);
				MathModuleCreator creator = moduleCreators[creatorIndex];
				IModule module = creator(random, region);
				modules.Add(module);
			}

			IModule result = Add(modules);
			return result; 
		}

		private IModule SinewaveCreator(Random random, RectangleF region)
		{
			Double regionSizeMeasure = RegionSizeMeasure(region);
			Double frequencyMin = regionSizeMeasure * 0.1;
			Double frequencyMax = regionSizeMeasure * 50;
			Double xMin = region.Left;
			Double xMax = region.Right;
			Double yMin = region.Top;
			Double yMax = region.Bottom;

			Sinewave sinewave = new Sinewave();
			sinewave.Frequency = RandomNextDouble(random, frequencyMin, frequencyMax);
			sinewave.Scale = RandomNextDouble(random, 0.5, 3);
			sinewave.X = RandomNextDouble(random, xMin, xMax);
			sinewave.Y = RandomNextDouble(random, yMin, yMax);
			sinewave.Z = 0;

			return sinewave;
		}

		private Distance DistanceCreator(Random random, RectangleF region)
		{
			Double xMin = region.Left;
			Double xMax = region.Right;
			Double yMin = region.Top;
			Double yMax = region.Bottom; 

			Distance distance = new Distance();
			Double x = RandomNextDouble(random, xMin, xMax);
			Double y = RandomNextDouble(random, yMin, yMax);
			Double z = 0;
			distance.SetOrigin(x, y, z);

			return distance;
		}

		private IModule GaussianCreator(Random random, RectangleF region)
		{
			Double regionSizeMeasure = RegionSizeMeasure(region);
			Double sigmaMin = regionSizeMeasure * 0.05;
			Double sigmaMax = regionSizeMeasure * 0.3;
			Double xMin = region.Left;
			Double xMax = region.Right;
			Double yMin = region.Top;
			Double yMax = region.Bottom; 

			Gaussian gaussian = new Gaussian();
			gaussian.Sigma = RandomNextDouble(random, sigmaMin, sigmaMax);
			gaussian.Scale = 3.0 / gaussian.Sigma;
			gaussian.X = RandomNextDouble(random, xMin, xMax);
			gaussian.Y = RandomNextDouble(random, yMin, yMax);
			gaussian.Z = 0;

			return gaussian;
		}
		private IModule PerlinCreator(Random random, RectangleF region)
		{
			Double regionSizeMeasure = RegionSizeMeasure(region);
			Double frequencyMin = regionSizeMeasure * 0.1;
			Double frequencyMax = regionSizeMeasure * 10;

			Perlin perlin = new Perlin();
			perlin.Seed = random.Next();
			perlin.NoiseQuality = NoiseQuality.High;
			perlin.Frequency = RandomNextDouble(random, frequencyMin, frequencyMax);

			// Ensure that octave * frequency is never greater than 10 times the
			// size measure for this region, otherwise the noise becomes too
			// high frequency to render nicely.
			// 
			// We also limit the maximum number of octaves to 5 for performance
			// reasons. 
			Int32 octaveMax = System.Math.Min(5, Convert.ToInt32(regionSizeMeasure * 10 / perlin.Frequency));
			perlin.OctaveCount = random.Next(1, octaveMax);
			perlin.Lacunarity = RandomNextDouble(random, 0.1, 5);
			perlin.Persistence = RandomNextDouble(random, 0.1, 5);

			return perlin;
		}

		private static Double RandomNextDouble(Random random, Double min, Double max)
		{
			Double portion = random.NextDouble();
			Double offset = portion * (max - min);
			Double result = min + offset;
			return result;
		}

		private static Double RegionSizeMeasure(RectangleF region)
		{
			Double regionSizeMeasure = System.Math.Sqrt(region.Width * region.Height);
			return regionSizeMeasure;
		}

		private static IModule Add(List<IModule> modules)
		{
			Add result = new Add();

			result.Modules = modules.ToArray();

			return result; 
		}
	}
}
